const babel = require("@babel/core");
const path = require("path");
const fs = require("fs");
const resolver = require("enhanced-resolve");

const root = path.resolve(__dirname, "../ant-design");
const entryDir = path.resolve(root, "./components");
const entryFile = "index.tsx";
// copy from `/ant-design/node_modules/@ant-design/tools/lib/`
const resolve = resolver.create.sync({
  extensions: [
    ".web.tsx",
    ".web.ts",
    ".web.jsx",
    ".web.js",
    ".ts",
    ".tsx",
    ".js",
    ".jsx",
    ".json",
  ],
  fileSystem: fs,
  alias: {
    "@ant-design/tools": path.resolve(__dirname, "../ant-design"),
  },
});

/**
 *
 * @param {string} p
 */
function getDirFromAbsolutePath(p) {
  return path.dirname(p);
}

/**
 *
 * @param {string} p
 */
function getFileFromAbsolutePath(p) {
  return p.split("/").pop();
}

function isNotJsFile(file) {
  return !(
    file.endsWith(".js") ||
    file.endsWith(".ts") ||
    file.endsWith(".jsx") ||
    file.endsWith(".tsx")
  );
}

/**
 *
 * @param {string} dir
 * @param {string} file
 * @param {Set<String>} set
 * @param {(dir: string, file: string) => void} callback
 */
function dfs(dir, file, set, callback) {
  if (isNotJsFile(file)) {
    return;
  }
  const target = path.resolve(dir, file);
  if (set.has(target)) {
    // avoid self-reference,
    // such as https://github.com/ant-design/ant-design/blob/master/components/version/index.tsx
    return;
  } else {
    set.add(target);
  }

  if (set.size % 100 == 0) {
    console.log(
      `Already processed ${set.size} files, now is deal with ${target}`
    );
  }

  const code = fs.readFileSync(target).toString("utf-8");
  const nodeHadRemoveTS = babel.transformSync(code, {
    presets: ["@babel/preset-typescript"],
    plugins: [
      [
        "@babel/plugin-proposal-decorators",
        {
          version: "2021-12",
        },
      ],
    ],
    ast: true,
    filename: target,
    configFile: false,
    sourceMaps: false,
    code: false,
    highlightCode: false,
    comments: false,
  });

  if (!nodeHadRemoveTS.ast) {
    return;
  }

  babel.traverse(nodeHadRemoveTS.ast, {
    enter(p) {
      let value = "";

      if (p.isImportDeclaration()) {
        value = p.node.source.value;
      } else if (p.isExportNamedDeclaration()) {
        if (!p.node.source) {
          return;
        }
        value = p.node.source.value;
      } else if (p.isCallExpression() && p.node.callee.name === "require") {
        if (typeof p.node.arguments?.[0].value === "string") {
          value = p.node.arguments[0].value;
        } else {
          console.log(target);
        }
      } else if (p.isCallExpression() && p.node.callee?.type === "Import") {
        if (typeof p.node.arguments?.[0].value === "string") {
          value = p.node.arguments[0].value;
        } else {
          console.log(target);
        }
      } else {
        return;
      }
      if (value === "") {
        return;
      }

      try {
        const next = resolve(dir, value);
        callback(dir, value);
        dfs(
          getDirFromAbsolutePath(next),
          getFileFromAbsolutePath(next),
          set,
          callback
        );
      } catch (err) {
        console.log(
          `[IGNORED FAILED] resolve ${value} in ${dir} failed, and ignored it.`
        );
      }
    },
  });
}

/**
 *
 * @param {(dir: string, file: string) => void} callback
 */
function run(callback) {
  dfs(entryDir, entryFile, new Set(), callback);
}

// ------------------------

const HEADER = `// DO NOT EDIT THIS FILE.
// It is auto-generated by <project>/bench/scripts/generator-rs-benchmark.js
`;

function generatorRsBenchmark() {
  let content =
    HEADER +
    `
#![feature(test)]
extern crate test;

#[cfg(test)] 
mod bench_test {

    use nodejs_resolver::{Resolver, Options, ResolveResult, RResult};
    use std::path::PathBuf;
    use test::Bencher;
    // use std::time::Instant;

    fn is_ok(result: RResult<ResolveResult>) {
      assert!(result.is_ok())
    }

    #[bench]
    fn ant_design_bench(b: &mut Bencher) {
        b.iter(|| {
          let resolver = Resolver::new(Options {
            extensions: vec![
              ".web.tsx",
              ".web.ts",
              ".web.jsx",
              ".web.js",
              ".ts",
              ".tsx",
              ".js",
              ".jsx",
              ".json",
            ].into_iter().map(String::from).collect(),
            ..Default::default()
          });

          // let start = Instant::now();
`;
  run(function (dir, file) {
    content += `
            is_ok(resolver.resolve(
                &PathBuf::from("${dir}"), 
                "${file}",
            ));
`;
  });
  content += `
          // println!("time cost: {:?} ms", start.elapsed().as_millis());// ms
        });
    }
}\n`;
  console.log("length", content.length);
  const rsFileStoredPath = path.resolve(__dirname, "../../tests/bench.rs");
  fs.writeFileSync(rsFileStoredPath, content);
}

function generatorEnhanceResolveBenchmark() {
  let content =
    HEADER +
    `
console.time('bench');
const path = require('path');
const { ResolverFactory } = require("enhanced-resolve");
const Benchmark = require('benchmark'); 

const root = path.resolve(__dirname, "./ant-design");

const resolver = ResolverFactory.createResolver({
  extensions: [
    '.web.tsx',
    '.web.ts',
    '.web.jsx',
    '.web.js',
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.json',
  ],
  fileSystem: require('fs'),
  alias: {
    "@ant-design/tools" : path.resolve(__dirname, '../ant-design'),
  },
})

async function resolve(dir, file) {
  return new Promise(res => {
    resolver.resolve({}, dir, file, {}, (_error, _result) => {
      res(undefined)
    })
  })
}

async function run() {
  const tasks = [];
`;
  run(function (dir, file) {
    content += `tasks.push(resolve('${dir}', '${file}'));\n`;
  });

  content += `
  await Promise.all(tasks);
};

// const suite = new Benchmark.Suite();
// suite
//   .add("EnhancedResolve", run)
//   .on('cycle', function(event) {
//     console.log(String(event.target));
//   })
//   .run();

run().then(() => {
  console.timeEnd('bench');
});
`;
  console.log("length", content.length);
  const jsFileStoredPath = path.resolve(__dirname, "../enhanceResolve.js");
  fs.writeFileSync(jsFileStoredPath, content);
}

function generatorESBuildResolveBenchMark() {
  let content =
    HEADER +
    `
const path = require('path');
const { build } = require('esbuild');
const Benchmark = require('benchmark'); 

const suite = new Benchmark.Suite();

async function run() {
  await build({
    stdin: {
      contents: '',
    },
    write: false,
    bundle: true,
    treeShaking: false,
    ignoreAnnotations: true,
    platform: 'node',
    plugins: [{
      name: 'resolve',
      setup(build) {
        build.onStart(async () => {
          console.time('bench')
          const tasks = [];
`;
  run(function (dir, file) {
    content += `tasks.push(build.resolve('${file}', { resolveDir: '${dir}', kind: 'import-statement' } ));\n`;
  });

  content += `
          await Promise.all(tasks);
          console.timeEnd('bench')
        })
      },
    }],
  })
};

run();

// suite
//   .add("ESBuildResolve", run)
//   .on('cycle', function(event) {
//     console.log(String(event.target));
//   })
//   .run({ 'async': true });
`;
  console.log("length", content.length);
  const jsFileStoredPath = path.resolve(__dirname, "../esbuildResolve.js");
  fs.writeFileSync(jsFileStoredPath, content);
}

function generatorESBuildNativeResolveBenchMark() {
  let content =
    HEADER +
    `
  package main

  import (
    "github.com/evanw/esbuild/pkg/api"
  )
  
  func main() {
    api.Build(api.BuildOptions{
      Stdin: &api.StdinOptions{
        Contents:   "",
      },
      Write:             false,
      TreeShaking:       api.TreeShakingFalse,
      Bundle:            true,
      IgnoreAnnotations: true,
      Platform:          api.PlatformNode,
      Plugins: []api.Plugin{{
        Name: "resolve",
        Setup: func(build api.PluginBuild) {
          build.OnStart(func() (api.OnStartResult, error) {
  `;
  run(function (dir, file) {
    content += `build.Resolve("${file}", api.ResolveOptions{ ResolveDir: "${dir}"})\n`;
  });

  content += `
          return api.OnStartResult{}, nil
          })
        },
      },
    },
  })
}
  `;
  console.log("length", content.length);
  const goFileStoredPath = path.resolve(
    __dirname,
    "../esbuildResolve_native.go"
  );
  fs.writeFileSync(goFileStoredPath, content);
}

// -------------------------------------------------

if (process.argv[2] === "rs") {
  generatorRsBenchmark();
} else if (process.argv[2] === "esbuild") {
  generatorESBuildResolveBenchMark();
} else if (process.argv[2] === "enhanced") {
  generatorEnhanceResolveBenchmark();
} else if (process.argv[2] === "esbuild_native") {
  generatorESBuildNativeResolveBenchMark();
  const cp = require("child_process");
  cp.spawnSync("go mod init esbuildResolve_native");
  cp.spawnSync("go get github.com/evanw/esbuild/pkg/api");
} else {
  throw Error("Input the correct argument");
}
